package arrays

import (
	"errors"
	"reflect"
	"sort"
)

// Array struct
//
// array wrap struct which can help call a serial of useful functions
//  arr := []int{3, 4, 2}
//	ret := From(arr).Map(...).Filter(...).ForEach(...)
type Array[T any] struct {
	arr []T
}

// change array to Array struct
//  arr := []int{3, 4, 2}
//	ret := From(arr)
func From[T any](arr []T) *Array[T] {
	ret := new(Array[T])
	ret.arr = arr
	return ret
}

// map the array and return the expected struct
//
//	arr := []int{3, 4, 2}
// 	ret := From(arr).Map(func(i int) any { return tuple{i, i} })
//
// 	ret = From(arr).Map(func(i int) any { return i * 2 })
func (self *Array[T]) Map(fn func(T) any) *Array[any] {
	newArr := []any{}

	for _, v := range self.arr {
		newArr = append(newArr, fn(v))
	}

	return &Array[any]{newArr}
}

// filter the array by fn condition
//
// 	arr := []int{3, 4, 2}
// 	ret := From(arr).Filter(func(i int) bool { return i > 3 })
// 	fmt.Println(ret)
func (self *Array[T]) Filter(fn func(item T) bool) *Array[T] {
	arr := []T{}
	for _, v := range self.arr {
		if fn(v) {
			arr = append(arr, v)
		}
	}
	self.arr = arr
	return self
}

// count for the array
func (self *Array[T]) Count() int {
	return len(self.arr)
}

// check if the array contains item given from the parameter
func (self *Array[T]) Contains(item T) bool {
	for _, v := range self.arr {
		t := reflect.ValueOf(v)
		v := reflect.ValueOf(item)
		if t == v {
			return true
		}
	}
	return false
}

// check if it there is any item satisfing fn
func (self *Array[T]) Any(fn func(item T) bool) bool {
	for _, v := range self.arr {
		if fn(v) {
			return true
		}
	}
	return false
}

// sort Array
func (self *Array[T]) SortBy(less func(i, j T) bool) *Array[T] {

	sort.Slice(self.arr, func(i, j int) bool {
		var v1 = self.arr[i]
		var v2 = self.arr[j]
		return less(v1, v2)
	})

	return self
}

// from Array struct to slice of T
//
// 	arr := []string{"banana", "apple", "pear"}
// 	ret := From(arr).Slice()
// 	fmt.Println(reflect.TypeOf(ret))
func (self *Array[T]) Slice() []T {
	return self.arr
}

// for each the array
//
//	arr := []int{3, 4, 2}
//	From(arr).ForEach(func(item, _ int) {
//		fmt.Println(item)
//	})
func (self *Array[T]) ForEach(fn func(item T, index int)) *Array[T] {

	for i, v := range self.arr {
		fn(v, i)
	}
	return self
}

// Parallel to run the item function
//
//	arr := []int{3, 4, 2}
//	From(arr).Parallel(func(item int) {
//		fmt.Println("Parallel run", item)
//	})
func (self *Array[T]) Parallel(fn func(item T)) *Array[T] {
	for _, v := range self.arr {
		go fn(v)
	}
	return self
}

// distinct the array by the key fn
//
// 	arr := []int{3, 4, 2, 5, 2, 3}
// 	count := From(arr).Distinct(func(item int) string {
// 		return fmt.Sprint(item)
// 	})
func (self *Array[T]) Distinct(fn func(item T) string) *Array[T] {

	set := make(map[string]T)
	for _, v := range self.arr {
		key := fn(v)
		set[key] = v
	}

	arr := []T{}
	for _, v := range set {
		arr = append(arr, v)
	}

	self.arr = arr
	return self
}

// take the data from start in count
func (self *Array[T]) Take(start int, count int) *Array[T] {
	arrLen := len(self.arr)
	if start > arrLen-1 || start+count > arrLen {
		self.arr = []T{}
	} else {
		self.arr = self.arr[start : start+count]
	}

	return self
}

// get the value by index, otherwise return error
func (self *Array[T]) Find(index int) (T, error) {
	if index > len(self.arr)-1 {
		var r T
		return r, errors.New("out of range")
	}
	return self.arr[index], nil
}

//  Reduce function
//
// 	arr := []int{3, 4, 2}
// 	value := From(arr).Reduce(0, func(acc int, cur int) int {
// 		return acc + cur
// 	})
func (self *Array[T]) Reduce(initial T, fn func(acc T, cur T) T) T {
	var value = initial
	for _, v := range self.arr {
		value = fn(value, v)
	}

	return value
}

// flat a array
//
// 	arr := [][]int{{3}, {4}, {3, 5}}
// 	newArr := From(arr).Flat(1).Slice()
// 	for i, v := range newArr {
// 		fmt.Println(i, v)
// 	}
func (self *Array[T]) Flat(depth int) *Array[any] {

	target := []any{}
	source := reflect.ValueOf(self.arr).Interface()
	Flatten(&target, source, depth)
	return &Array[any]{target}
}

// map + flat for array
//
// 	arr := []string{"it's Sunny in", "", "California"}
// 	newArr := From(arr).FlatMap(func(item string) any {
// 		return strings.Split(item, " ")
// 	}, 1).Slice()
// 	for i, v := range newArr {
// 		fmt.Println(i, v)
// 	}
func (self *Array[T]) FlatMap(fn func(item T) any, depth int) *Array[any] {
	return self.Map(fn).Flat(depth)
}
